<template>
  <div class="container" style="height: 100%">
    <div class="header">
      <CommonHeader/>
    </div>
    <div class="main" style="overflow: scroll">
      <el-backtop target=".main" :bottom="100" :right="100" style="border: #747bff">
        <img src="../../public/ship02.jpg" width="35" height="35" style="border-radius: 100%"/>
      </el-backtop>
      <el-row :gutter="20">
        <el-col style="background-color: #263238" :span="13">
          <div class="title">
            <h1>为了更美好的明天</h1>
            <div style="margin: 15px 0 0 15px;display: flex">
              <img src="../../public/ikun.jpg" width="50" height="50" style="border-radius: 100px"/>
              <div style="display: flex;flex-direction: column;margin:5px 0 0 10px">
                <span>ikun</span>
                <span>2023年03月11日 14:57</span>
              </div>
            </div>
          </div>
          <div class="content" style="margin-top: 25px">
            <MdEditor
                :markedHeadingId="markedHeadingId"
                editorId="my-editor"
                v-model="markdown"
                previewOnly
                showCodeRowNumber
                previewTheme="mk-cute"
            />
          </div>
        </el-col>
        <el-col style="background-color: #263238;margin-left: 20px" :span="6">
          <div style="margin: 15px 0 0 15px;display: flex">
            <img src="../../public/ikun.jpg" width="50" height="50" style="border-radius: 100px"/>
            <div style="display: flex;flex-direction: column;margin:5px 0 0 10px">
              <span>ikun</span>
              <span>一个真正的Man，清楚自己要做什么</span>
              <span><el-tag style="margin: 10px 10px 0 0;font-size: 18px">文章数</el-tag>1024</span>
              <span><el-tag style="margin: 10px 10px 0 0;font-size: 18px">IP</el-tag>四川</span>
            </div>
          </div>
          <el-affix :offset="10">
            <div class="toc" style="background-color: #263238;margin-top: 50px">
              <MdCatalog editorId="my-editor" :scrollElement="scrollElement" theme="dark"/>
            </div>
          </el-affix>
        </el-col>
      </el-row>
    </div>
  </div>
</template>

<script setup>
import CommonHeader from "../components/CommonHeader.vue";
import {ref, getCurrentInstance} from "vue";
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';


const {proxy} = getCurrentInstance()
const MdCatalog = MdEditor.MdCatalog;
const scrollElement = document.documentElement;
const markedHeadingId = (_text, _level, index) => `heading-${index}`;
MdEditor.config({
  markedRenderer(renderer) {
    renderer.heading = (text, level, _raw, _s, _index, headingId) => {
      return `<h${level} id="${headingId}">${text}</h${level}>`;
    };
    return renderer;
  }
});
const markdown = ref('### 一、环境搭建\n' +
    '\n' +
    '#### 1.创建Spring Boot项目\n' +
    '\n' +
    '#### 2.整合Spring Security\n' +
    '\n' +
    '引入依赖\n' +
    '\n' +
    '```xml\n' +
    '<dependency>\n' +
    '    <groupId>org.springframework.boot</groupId>\n' +
    '    <artifactId>spring-boot-starter-security</artifactId>\n' +
    '</dependency>\n' +
    '```\n' +
    '\n' +
    '配置文件 内存用户名和密码硬编码\n' +
    '\n' +
    '```yaml\n' +
    'spring:\n' +
    '  security:\n' +
    '    user:\n' +
    '      name: user\n' +
    '      password: 1234 # 自定义密码\n' +
    '\t  roles: admin,user\n' +
    '```\n' +
    '\n' +
    '### 二、UserDetail\n' +
    '\n' +
    '#### 1.创建实体类LoginUser\n' +
    '\n' +
    '```java\n' +
    '@Data\n' +
    '@AllArgsConstructor\n' +
    '@NoArgsConstructor\n' +
    'public class LoginUser implements UserDetails {\n' +
    '\n' +
    '    //表对应的实体类User\n' +
    '    private User user;\n' +
    '    \n' +
    '    //拥有的权限\n' +
    '  \tList<String> permissions;\n' +
    '\n' +
    '    public LoginUser(User user, List<String> permissions) {\n' +
    '        this.user = user;\n' +
    '        this.permissions = permissions;\n' +
    '    }\n' +
    '\n' +
    '    @JsonIgnore\n' +
    '    List<SimpleGrantedAuthority> authorities;\n' +
    '\n' +
    '\n' +
    '    @Override\n' +
    '    public Collection<? extends GrantedAuthority> getAuthorities() {\n' +
    '        if (authorities!=null){\n' +
    '            return authorities;\n' +
    '        }\n' +
    '\n' +
    '        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());\n' +
    '        return authorities;\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public String getPassword() {\n' +
    '        return user.getPassword();\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public String getUsername() {\n' +
    '        return user.getUsername();\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public boolean isAccountNonExpired() {\n' +
    '        return true;\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public boolean isAccountNonLocked() {\n' +
    '        return true;\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public boolean isCredentialsNonExpired() {\n' +
    '        return true;\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public boolean isEnabled() {\n' +
    '        return true;\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '#### 2.创建UserDetailServiceImpl\n' +
    '\n' +
    '**根据用户名从数据库查询该用户信息**\n' +
    '\n' +
    '```java\n' +
    '@Service\n' +
    'public class UserDetailServiceImpl implements UserDetailsService {\n' +
    '\n' +
    '    @Resource\n' +
    '    private UserMapper userMapper;\n' +
    '\n' +
    '    @Override\n' +
    '    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n' +
    '        //1.根据用户名获取数据库中的用户\n' +
    '        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();\n' +
    '        queryWrapper.eq(User::getUsername,username);\n' +
    '        User user = userMapper.selectOne(queryWrapper);\n' +
    '        if (ObjectUtil.isNotNull(user)) {\n' +
    '\n' +
    '        }else {\n' +
    '            throw new RuntimeException("用户名错误!");\n' +
    '        }\n' +
    '      \n' +
    '\t\t//2.查询权限信息 这里是假数据 应该从数据库中查询拥有的权限\n' +
    '        List<String> list = new ArrayList<>(Arrays.asList("sayIndex","delGoods"));\n' +
    '        \n' +
    '        //3.返回userDetails\n' +
    '        return new LoginUser(user,list);\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '### 四、过滤器配置\n' +
    '\n' +
    '```java\n' +
    '@Component\n' +
    'public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {\n' +
    '\n' +
    '    //注入redis工具类\n' +
    '    @Resource\n' +
    '    private RedisUtil redisUtil;\n' +
    '\n' +
    '    @Override\n' +
    '    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {\n' +
    '        //1.获取token 请求头Header中的token\n' +
    '        String token = request.getHeader("token");\n' +
    '        if (StrUtil.isBlank(token)) {\n' +
    '            //放行，让后面的过滤器执行\n' +
    '            filterChain.doFilter(request, response);\n' +
    '            return;\n' +
    '        }\n' +
    '\n' +
    '        //2.解析token\n' +
    '        String userId;\n' +
    '        try {\n' +
    '            Claims claims = JwtUtil.getClaimsByToken(token);\n' +
    '            //3.获取claims的subject主题 userId\n' +
    '            userId = claims.getSubject();\n' +
    '        } catch (Exception e) {\n' +
    '            throw new CustomerException(Constants.CODE_600,"token不合法");\n' +
    '        }\n' +
    '\n' +
    '        //4.redis获取用户信息\n' +
    '        String jsonStr = (String) redisUtil.get("login:" + userId);\n' +
    '        if (StrUtil.isBlank(jsonStr)){\n' +
    '            throw new CustomerException(Constants.CODE_401,"当前用户未登录");\n' +
    '        }\n' +
    '        LoginUser loginUser = JSONUtil.toBean(jsonStr, new TypeReference<LoginUser>() {\n' +
    '        }, true);\n' +
    '\n' +
    '        //4.封装Authentication\n' +
    '        UsernamePasswordAuthenticationToken authentication =\n' +
    '                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());\n' +
    '\n' +
    '        //5.存入SecurityContextHolder\n' +
    '        SecurityContextHolder.getContext().setAuthentication(authentication);\n' +
    '        //放行，让后面的过滤器执行\n' +
    '        filterChain.doFilter(request, response);\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '### 五、配置类SecurityConfig\n' +
    '\n' +
    '```java\n' +
    '@Configuration\n' +
    '//配置了该注解 就可以在controller中的方法上配置注解@PreAuthorize("hasAuthority(\'sayIndex1\')")\n' +
    '@EnableGlobalMethodSecurity(prePostEnabled = true)\n' +
    '@EnableWebSecurity\n' +
    'public class SecurityConfig{\n' +
    '\n' +
    '    @Resource\n' +
    '    private AuthenticationConfiguration authenticationConfiguration;\n' +
    '    @Resource\n' +
    '    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;\n' +
    '\n' +
    '    @Bean\n' +
    '    public AuthenticationManager authenticationManager() throws Exception {\n' +
    '        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();\n' +
    '        return authenticationManager;\n' +
    '    }\n' +
    '\n' +
    '    @Bean\n' +
    '    public PasswordEncoder passwordEncoder(){\n' +
    '        return new BCryptPasswordEncoder();\n' +
    '    }\n' +
    '\n' +
    '    @Bean\n' +
    '    public WebSecurityCustomizer webSecurityCustomizer(){\n' +
    '        return (web) -> web.ignoring().antMatchers("/images/**","/js/**","/webjars/**");\n' +
    '    }\n' +
    '\n' +
    '    @Bean\n' +
    '    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{\n' +
    '        http\n' +
    '                //关闭csrf\n' +
    '                .csrf().disable()\n' +
    '                //不通过session获取SecurityContext\n' +
    '                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)\n' +
    '                .and()\n' +
    '                .authorizeRequests()\n' +
    '                //对于登录接口允许匿名访问\n' +
    '                .antMatchers("/user/login").anonymous()\n' +
    '                //除上面外的所有请求全部需要鉴权认证\n' +
    '                .anyRequest().authenticated();\n' +
    '        //把自己的过滤器放在最前面\n' +
    '        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);\n' +
    '         //告诉security如何处理异常\n' +
    '        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)\n' +
    '                .accessDeniedHandler(accessDeniedHandler);\n' +
    '        //允许跨域\n' +
    '        http.cors();\n' +
    '        return http.build();\n' +
    '    }\n' +
    '\n' +
    '//    @Bean\n' +
    '//    public SecurityFilterChain securityFilterChain(){\n' +
    '//        List<Filter> filters = new ArrayList<>();\n' +
    '//        return new DefaultSecurityFilterChain(new AntPathRequestMatcher("/**"),filters);\n' +
    '//    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '### 六、LoginServiceImpl编写\n' +
    '\n' +
    '```java\n' +
    '@Service\n' +
    'public class LoginServiceImpl implements LoginService {\n' +
    '\n' +
    '    @Resource\n' +
    '    private AuthenticationManager authenticationManager;\n' +
    '    @Resource\n' +
    '    private RedisUtil redisUtil;\n' +
    '\n' +
    '    @Override\n' +
    '    public Result logout() {\n' +
    '        //根据authentication获取userId\n' +
    '        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();\n' +
    '        LoginUser loginUser = (LoginUser) authentication.getPrincipal();\n' +
    '\n' +
    '        //清除redis中的用户数据信息\n' +
    '        redisUtil.del("login:"+loginUser.getUser().getId());\n' +
    '        return Result.success("退出成功");\n' +
    '    }\n' +
    '\n' +
    '    @Override\n' +
    '    public Result login(User user) {\n' +
    '        //使用ProviderManager auth方法进行验证\n' +
    '        UsernamePasswordAuthenticationToken authenticationToken =\n' +
    '                new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());\n' +
    '        Authentication authentication = authenticationManager.authenticate(authenticationToken);\n' +
    '\n' +
    '        //校验失败\n' +
    '        if (ObjectUtil.isNull(authentication)){\n' +
    '            throw new CustomerException(Constants.CODE_600,"用户名或密码错误，请重试");\n' +
    '        }\n' +
    '\n' +
    '        //自己生成jwt给前端\n' +
    '        LoginUser loginUser = (LoginUser) authentication.getPrincipal();\n' +
    '        String token = JwtUtil.generateToken(loginUser.getUser().getId());\n' +
    '        Map<String,Object> map = new HashMap<>();\n' +
    '        map.put("token",token);\n' +
    '        map.put("user",loginUser);\n' +
    '\n' +
    '        //系统用户相关所有信息放入redis\n' +
    '        redisUtil.set("login:"+loginUser.getUser().getId(),JSONUtil.toJsonStr(loginUser));\n' +
    '\n' +
    '        return Result.success(map);\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '### 七、权限注解\n' +
    '\n' +
    '```java\n' +
    '@RestController\n' +
    'public class IndexController {\n' +
    '\t//在配置类中开启了@EnableGlobalMethodSecurity(prePostEnabled = true)\n' +
    '    @PreAuthorize("hasAuthority(\'sayIndex1\')") //单引号里是权限的名称，存在于数据库中\n' +
    '    @GetMapping("index")\n' +
    '    public String index(){\n' +
    '        return "Hello Security";\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '### 八、自定义失败处理\n' +
    '\n' +
    'AuthenticationEntryPointImpl\n' +
    '\n' +
    '```java\n' +
    '@Component\n' +
    'public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {\n' +
    '\n' +
    '    @Override\n' +
    '    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {\n' +
    '        //给前端ResponseResult 的json\n' +
    '        Result result = new Result(Constants.CODE_401, "登陆认证失败了，请重新登陆！",null);\n' +
    '        String json = JSONUtil.toJsonStr(result);\n' +
    '        WebUtil.renderString(response,json);\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    'AccessDeniedHandlerImpl\n' +
    '\n' +
    '```java\n' +
    '@Component\n' +
    'public class AccessDeniedHandlerImpl implements AccessDeniedHandler {\n' +
    '    @Override\n' +
    '    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {\n' +
    '//给前端ResponseResult 的json\n' +
    '        Result result = new Result(Constants.CODE_403, "您权限不足！",null);\n' +
    '        String json = JSONUtil.toJsonStr(result);\n' +
    '        WebUtil.renderString(response,json);\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '配置SecurityConfig配置类\n' +
    '\n' +
    '```\n' +
    '第五步已经配置\n' +
    '```\n' +
    '\n' +
    '### 九、跨域\n' +
    '\n' +
    '#### 1.对Boot进行配置\n' +
    '\n' +
    '```java\n' +
    '@Configuration\n' +
    'public class WebMvcConfig implements WebMvcConfigurer {\n' +
    '\n' +
    '    @Override\n' +
    '    public void addCorsMappings(CorsRegistry registry) {\n' +
    '      // 设置允许跨域的路径\n' +
    '        registry.addMapping("/**")\n' +
    '                // 设置允许跨域请求的域名\n' +
    '                .allowedOriginPatterns("*")\n' +
    '                // 是否允许cookie\n' +
    '                .allowCredentials(true)\n' +
    '                // 设置允许的请求方式\n' +
    '                .allowedMethods("GET", "POST", "DELETE", "PUT")\n' +
    '                // 设置允许的header属性\n' +
    '                .allowedHeaders("*")\n' +
    '                // 跨域允许时间\n' +
    '                .maxAge(3600);\n' +
    '    }\n' +
    '}\n' +
    '```\n' +
    '\n' +
    '#### 2.开启SpringSecurity的跨域访问\n' +
    '\n' +
    '```java\n' +
    '//在Security配置类中开启\n' +
    '//第五步已经pei\n' +
    '```\n' +
    '\n')
</script>

<style lang="less" scoped>

.container {
  .main {
    width: 85%;
    height: 100vh;
    margin-left: 15%;

    .title {
      margin-left: 20px;
    }

    .el-col-6 {
      height: 200px;
    }

    .el-col {
      padding: 20px;
    }
  }

  .header {
    margin-right: 20px;
  }

  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background: url("../public/post/star03.jpg") no-repeat;
  background-size: 100% 100%;
}
</style>